//////////////////////////////////////////////
// main.cpp
//
//////////////////////////////////////////////

/// Includes ---------------------------------

// nkLog
#include <NilkinsLog/Loggers/ConsoleLogger.h>

// nkScripts
#include <NilkinsScripts/Log/LogManager.h>

#include <NilkinsScripts/Environments/Functions/Function.h>

#include <NilkinsScripts/Environments/Namespaces/Namespace.h>

#include <NilkinsScripts/Environments/UserTypes/UserType.h>

#include <NilkinsScripts/Environments/Environment.h>
#include <NilkinsScripts/Environments/EnvironmentManager.h>

#include <NilkinsScripts/Interpreters/Interpreter.h>

#include <NilkinsScripts/Scripts/Script.h>
#include <NilkinsScripts/Scripts/ScriptManager.h>

// Standards
#include <memory>
#include <iostream>

/// Structures -------------------------------

// Class we will proxy through the user type
class Data
{
	public :

		std::string _label = "I" ;
		int _i = 0 ;

		void printLabel ()
		{
			std::cout << _label << " : " << std::to_string(_i) << std::endl ;
		}
} ;

/// Function ---------------------------------

int main ()
{
	// Prepare logging capabilities
	std::unique_ptr<nkLog::Logger> logger = std::make_unique<nkLog::ConsoleLogger>() ;
	nkScripts::LogManager::getInstance()->setReceiver(logger.get()) ;

	// Prepare the environment we will with
	nkScripts::Environment* env = nkScripts::EnvironmentManager::getInstance()->createOrRetrieve("firstEnv") ;
	env->setEnvironmentFor(nkScripts::INTERPRETER::LUA) ;

	// Create a namespace to work with
	nkScripts::Namespace* ns = env->setNamespace("nkTutorial") ;

	// A namespace is very close to environment, we can set a function on it
	nkScripts::Function* func = ns->setFunc("foo") ;
	func->addParameter(nkScripts::FUNCTION_PARAMETER_TYPE::STRING) ;

	func->setFunction
	(
		[&logger] (const nkScripts::DataStack& stack) -> nkScripts::OutputValue
		{
			logger->log(stack[0]._valString, "foo") ;

			return nkScripts::OutputValue::VOID ;
		}
	) ;

	// Try to use the namespace within a script
	nkScripts::Script* script = nkScripts::ScriptManager::getInstance()->createOrRetrieve("firstScript") ;
	script->setTargetInterpreter(nkScripts::INTERPRETER::LUA) ;
	script->setSources
	(
		R"eos(
			nkTutorial.foo("Calling from Lua !") ;
		)eos"
	) ;
	script->load() ;
	env->execute(*script) ;

	// Usage of UserType now
	nkScripts::UserType* type = env->setUserType("nkTutorial::Data") ;

	// Add a method on the type, that we will populate like a function
	nkScripts::Function* method = type->addMethod("printLabel") ;

	method->setFunction
	(
		[] (const nkScripts::DataStack& stack) -> nkScripts::OutputValue
		{
			// A method will always have the object it's called from as the first parameter in the stack
			Data* object = (Data*)stack[0]._valUser._userData ;
			object->printLabel() ;

			return nkScripts::OutputValue::VOID ;
		}
	) ;

	// Set the object in the environment from C++ to be able to address it
	Data data ;

	env->setObject("data", "nkTutorial::Data", &data) ;

	// New script to address the object
	script->unload() ;
	script->setSources
	(
		R"eos(
			data:printLabel() ;

			-- Or : nkTutorial.Data.printLabel(data) ;
			-- But version demonstrated is more robust as Lua will warn you if data is nil
		)eos"
	) ;
	script->load() ;
	env->execute(*script) ;

	// Demonstrate the constructor and destructor declarations and usage
	type->setConstructor
	(
		[] (const nkScripts::DataStack& stack) -> void*
		{
			return new Data () ;
		}
	) ;

	type->setDestructor
	(
		[] (void* data)
		{
			delete (Data*)data ;
		}
	) ;

	// Test them in a script
	script->unload() ;
	script->setSources
	(
		R"eos(
			local d = nkTutorial.Data.new() ;
			d:printLabel() ;
		)eos"
	) ;
	script->load() ;
	env->execute(*script) ;

	// Demonstrates how to specify ownership from an OutputValue
	nkScripts::Function* staticMethod = type->addStaticMethod("duplicate") ;
	staticMethod->addParameter(nkScripts::FUNCTION_PARAMETER_TYPE::USER_DATA_PTR, "nkTutorial::Data") ;

	staticMethod->setFunction
	(
		[] (const nkScripts::DataStack& stack) -> nkScripts::OutputValue
		{
			// Cast the input
			Data* input = (Data*)stack[0]._valUser._userData ;

			Data* result = new Data () ;
			result->_label = input->_label + "d" ;
			result->_i = input->_i + 1 ;

			// By returning true, we mean that the script is to be considered owner of the memory we are returning
			return nkScripts::OutputValue(result, "nkTutorial::Data", true) ;
		}
	) ;

	// Test that function
	script->unload() ;
	script->setSources
	(
		R"eos(
			local d = nkTutorial.Data.duplicate(data) ;
			d:printLabel() ;
		)eos"
	) ;
	script->load() ;
	env->execute(*script) ;

	// Small pause to be able to witness the console
	system("pause") ;

	return 0 ;
}